#include <stdlib.h>
#include <stdio.h>
#include <nds.h>

#include <string.h>

// common headers
#include "common.h"

// local headers
#include "slibs.h"
#include "binarylist.h"
#include "improvedextensionlist.h"

#include "plugin.h"

typedef struct {    // the header in the file
    char magic[12];    // magic string

    void *dataStart;
    void *dataEnd;
    void *interworkStart;
    void *interworkEnd;
    void *gotStart;
    void *gotEnd;
    void *bssStart;
    void *bssEnd;

    void *init;
    void *query;
    void *main;
    void *deinit;
} sPHeader;

char *getPPath(char *name) {
    if(!name) return NULL;

    static char path[256];

    snprintf(path, 256, "/data/SunOS/plugins/%s/%s.sunds", name, name);

    return path;
}

void RelocatePlugAdresses(void* buf) {
    s64 offset;
    u8 **address;
    u8 *_Start;
    u8 *_End;
    sPHeader *PH=(sPHeader *)buf;

    offset = (void *)PH - (void *)PH->dataStart;

    _Start = PH->dataStart;
    _End = PH->bssEnd;

    // Fix all pointers to the offsets from the location of this interface
    #define fixoffset(a) if(a) (a)+=offset
    fixoffset(PH->dataStart);
    fixoffset(PH->dataEnd);
    fixoffset(PH->interworkStart);
    fixoffset(PH->interworkEnd);
    fixoffset(PH->gotStart);
    fixoffset(PH->gotEnd);
    fixoffset(PH->bssStart);
    fixoffset(PH->bssEnd);

    fixoffset(PH->init);
    fixoffset(PH->query);
    fixoffset(PH->main);
    fixoffset(PH->deinit);

#if 0
    // Fix the interworking glue section
    for(address = (u8**)PH->interworkStart; address < (u8**)PH->interworkEnd; address++)
        if(_Start <= *address && *address < _End)
            *address += offset;

    // Fix the global offset table section (and the data between got and bss)
    for(address = (u8**)PH->gotStart; address < (u8**)PH->dataEnd; address++)
        if(_Start <= *address && *address < _End)
            *address += offset;
#else
u32 nr[3]={0,0,0};
    // Fix all
    for(address = (u8**)PH->dataStart; address < (u8**)PH->dataEnd; address++)
        if(_Start <= *address && *address < _End) {
if((u8 *)PH->interworkStart <= *address && *address < (u8 *)PH->interworkEnd) nr[0]++;
if((u8 *)PH->gotStart <= *address && *address < (u8 *)PH->gotEnd) nr[1]++;
if((u8 *)PH->bssStart <= *address && *address < (u8 *)PH->bssEnd) nr[2]++;
            *address += offset;
        }
/*iprintf(":%d(%d),%d(%d),%d(%d)",
    nr[0], PH->interworkEnd-PH->interworkStart,
    nr[1], PH->gotEnd-PH->gotStart,
    nr[2], PH->bssEnd-PH->bssStart
);*/

/*scanKeys();
while(!(keysDown() & KEY_B)) scanKeys();
scanKeys();*/

#endif
    #undef fixoffset
}

int initPlugin(sPlugin *plug) {
    if(!plug || plug->_data) return 1;

    size_t szb, szf, ret;
    u8 *buf;
    sPHeader *PH;

// <-- open file
    FILE *fd=fopen(getPPath(plug->name), "rb");
    if(!fd) return 2;
// -->

// <-- get the full size with the bss section
    {
        sPHeader _PH;

        ret=fread((void *)&_PH, 1, sizeof(sPHeader), fd);
        if(ret!=sizeof(sPHeader)) {
            fclose(fd);
            return 3;
        }

        if(strncmp(_PH.magic, "SunOS_plugin", 12)) {    // not a valid plugin
            fclose(fd);
            return 4;
        }

        szb=_PH.bssEnd - _PH.dataStart;
    }
// -->

// allocate enough memory (data + bss)
    buf=xmalloc(szb);

// <-- get file content
    fseek(fd, 0, SEEK_END);
    szf=ftell(fd);
    rewind(fd);

    ret=fread(buf, 1, szf, fd);
    fclose(fd);
    if(ret!=szf) {
        xfree(buf);
        return 5;
    }
// -->

// relocate adresses
    RelocatePlugAdresses(buf);

    PH=(sPHeader *)buf;

// Initialise the BSS to 0
    memset(PH->bssStart, 0, (u8*)PH->bssEnd - (u8*)PH->bssStart);

// copy info in the sPlugin structure
    plug->_data=buf;
    plug->init=PH->init;
    plug->query=PH->query;
    plug->main=PH->main;
    plug->deinit=PH->deinit;

#if 0
{
#if 0
int i;
for(i=0; i<24; i++) {
    printf("\n:0x%08x", ((u32 *)plug->init)[i]);
}
scanKeys();
while(!(keysDown() & KEY_B)) scanKeys();
scanKeys();
#endif
#ifndef NITRO
// with this fucking write before any binary load, SunOS is very stable !!!!!!!!!
char rr[256];
sprintf(rr, "/data/temp/%s.sunds.dump", plug->name);
FILE *f=fopen(rr, "w+");
if(f) {
fwrite(buf, 1, szf, f);
fclose(f);
}
#endif
}
#endif

// call the init function
    if(plug->init(&_libs)) {
        if(plug->_data) xfree(plug->_data);
        plug->_data=NULL;
        return 6;
    }

    return 0;
}

sPlugin *getPluginByExt(char *fn) {
    if(!fn) return NULL;

    sIEL *el=extslist;
    if(!el) return NULL;
    size_t extl=(size_t)-1;
    u32 hash=0;
    size_t fnl=strlen(fn);
    char *ext=NULL;

    while(el) {
        if(el->extl!=extl) {
            extl=el->extl;
            if(extl>fnl) break;

            ext=&fn[fnl-extl];

            hash=IEL_hash(ext, extl);
        }

        if(hash==el->hash && !strncasecmp(el->ext, ext, extl))
            return el->plug;

        el=el->next;
    }

    return NULL;
}

int runPlugin(sPlugin *plug, int argc, char *argv[]) {
    iprintf("\n{\n  Loading \"%s\"...", plug->name);
    if(initPlugin(plug)) {
        iprintf("failed");
        return 1;
    }
    iprintf("ok");

    iprintf("\n  Plugin infos :");
    iprintf("\n   %s v%s by %s.",
        (char *)plug->query(QUERY_NAME),
        (char *)plug->query(QUERY_VERSION_STR),
        (char *)plug->query(QUERY_AUTHOR)
    );

    iprintf("\n  Running...");

    int ret=plug->main(argc, argv);

    if(ret) iprintf("failed(%d)", ret);
    else iprintf("ok");

    iprintf("\n  Unloading...");
    deinitPlugin(plug);
    iprintf("ok\n}");

    return ret;
}

void deinitPlugin(sPlugin *plug) {
    if(!plug) return;

    if(plug->deinit) plug->deinit();
    if(plug->_data) xfree(plug->_data);
    plug->_data=NULL;
}

